import { string } from "prop-types";

namespace a {
    // interface Person {
    //     firstName: string;
    //     lastName: string;
    // }
    // function greeter(person: Person) {
    //     return "Hello, " + person.firstName + " " + person.lastName;
    // }
    // let user = { firstName: "Jane", lastName: "User" };
    // document.body.innerHTML = greeter(user);
    enum Gender {
        GIRL,
        BOY
    }
    // console.log(`李雷是${Gender.BOY}`);
    // console.log(`韩梅梅是${Gender.GIRL}`);
    console.log(`韩梅梅是${Gender[0]}`)
    enum Week {
        MONDAY = 1,
        TUESDAY = 2
    }
    let x: number;
    x = 1;
    // x = undefined;
    // x = null;
    // 联合类型
    let name: string | number;
    // name.toString
    name = 2;
    // name.toFixed(2)
    // 类型断言
    let name2: string | number;
    // (name2 as number).toFixed(2) // 判定name2为number类型
    // 字面量类型 => 可以把字符串，数字,布尔类型组成一个联合类型
    type ZType = 1 | 'one' | boolean;
    let t1: ZType = 1;
    let t2: ZType = 'one';
    let t3: ZType = false;
    // 字面量类型
    type str = 'a' | 'b' | boolean;
    type GetUserNameFunction = (x: string, y: string) => string
    let getUserNameFunction: GetUserNameFunction = function (x: string, y: string): string {
        return x + y
    }
    // getUserNameFunction()
    console.log(getUserNameFunction('雷', '超'))
    // console.log(`今天是星期${Week.MONDAY}`);
    function print(name?: string, age: number = 25): void {
        console.log(name, age)
    }
    print('阿萨')
    function sum(...args: number[]): number {
        return args.reduce((pre, cur) => pre + cur, 0)
    }
    console.log(sum(1, 2, 3, 4, 5))
    // 重载: 在java中指的是两个或者两个以上的同名函数,参数列表不一样
    // 重载在ts中指的是为同一个函数中提供多个相同的函数类型定义,并且紧紧的靠在一起
    let obj: any = {}
    function attr(val: string): void;
    function attr(val: number): void;
    function attr(val: any): void {
        if (typeof val === 'string') {
            // console.log('我是字符串的')
            obj.name = val
        } else if (typeof val === 'number') {
            obj.age = val
        } else {
            obj.other = val
        }
    }
    attr('雷超')
    attr(27)
    // attr(true)
    class Person {
        // name:string;
        /**
         * 构造函数的作用是:给实例化属性赋值和实例创建的时候会执行
         */
        constructor(public name?: string) { } // 构造函数属性的初始化
        getName(): void {
            console.log(this.name)
        }
    }
    let p1 = new Person()
    p1.name = 'ssd'
    p1.getName()
    class User {
        myname: string;
        constructor(myname: string) {
            this.myname = myname;
        }
        get name() {
            return this.myname;
        }
        set name(value: string) {
            this.myname = value;
        }
    }

    let user = new User('zhufeng');
    user.name = 'jiagou';
    console.log(user.name);
    class Animal {
        public readonly name: string // name属性是只读属性，不可修改
        constructor(name: string) {
            this.name = name;
        }
        // changeName(name:string){
        //     this.name = name;
        // }
    }

    let a = new Animal('zhufeng');
    // a.changeName('jiagou');
    // 继承

}
console.log('==============')
namespace b {
    class Person2 {
        name: string;//定义实例的属性，默认省略public修饰符
        age: number;
        static interest: string = '哈哈';
        constructor(name: string, age: number) {//构造函数
            this.name = name;
            this.age = age;
        }
        getName(): string {
            return this.name;
        }
        setName(name: string): void {
            this.name = name;
        }
    }
    class Student extends Person2 {
        no: number;
        constructor(name: string, age: number, no: number) {
            super(name, age);
            this.no = no;
        }
        static getInternet() {
            // console.log(super)
        }
        getNo(): number {
            return this.no;
        }
        getInfo(): void {
            let name = super.getName()
            console.log(name)
            // console.log(`姓名:${this.name},${this.age},${this.no}`)
        }
    }
    let s1 = new Student('雷超', 27, 100);
    s1.getInfo()
}
// 类装饰器
namespace c {
    interface Person {
        eat: () => void
    }
    function modifyPerson(Person: any) {
        Person.prototype.eat = () => {
            console.log('eat吃饭')
        }
    }
    @modifyPerson
    class Person {
        constructor(public name?: string) { }
    }
    let p: Person = new Person()
    console.log(p.name)
    p.eat()
}
console.log('=======属性密爱哦暑期=======')
// 属性描述器
namespace d {
    function upperCase(instance: any, propName: string) {
        console.log(propName)
        // console.log('是否是Person实例:', instance instanceof Person)
        instance[propName + '1'] = 'prop'
    }
    function noEnumerable(target: any, propName: string, propertyDescriptor: PropertyDescriptor) {
        // return undefined
        console.log(target, propName, propertyDescriptor)
    }
    class Person {
        // 属性描述器
        @upperCase
        name: string = 'zhufeng'
        public static age: number = 10;
        constructor() { }
        @noEnumerable
        getName() {
            console.log(this.name)
        }
        // @toNumber
        // sum(...args: any[]) {
        //     return args.reduce((accu: number, item: number) => accu + item, 0);
        // }

    }
    let p: Person = new Person();
    // for (let attr in p) {
    //     console.log('attr=', attr);
    // }
    // p.name = 'jiagou';
    p.getName();
    // console.log(p.sum("1", "2", "3"));
}
console.log('=======参数装饰器开始========')
namespace d {
    interface Person {
        age: number
    }
    // 第一个参数对于静态属性来说是构造函数，对于实例属性来说是类的原型
    function addAge(target: any, paramsName: string, paramsIndex: number) {
        console.log(target)
        console.log(paramsName)
        console.log(paramsIndex)
        target.age = 27 // 添加一个age属性
    }
    class Person {
        login(userName: string, @addAge password: string) {
            console.log(userName, password)
        }
    }
    let p = new Person()
    console.log(p.age)
    p.login('雷超', '123456')
}
console.log('=======参数装饰器结束========')
// 装饰器的顺序
namespace e {
    function Class1Decorator() {
        return function (target: any) {
            console.log("类1装饰器");
        }
    }
    function Class2Decorator() {
        return function (target: any) {
            console.log("类2装饰器");
        }
    }
    function MethodDecorator() {
        return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
            console.log("方法装饰器");
        }
    }
    function Param1Decorator() {
        return function (target: any, methodName: string, paramIndex: number) {
            console.log("参数1装饰器");
        }
    }
    function Param2Decorator() {
        return function (target: any, methodName: string, paramIndex: number) {
            console.log("参数2装饰器");
        }
    }
    function PropertyDecorator(name: string) {
        return function (target: any, propertyName: string) {
            console.log(name + "属性装饰器");
        }
    }

    @Class1Decorator()
    @Class2Decorator()
    class Person {
        @PropertyDecorator('name')
        name: string = 'zhufeng';
        @PropertyDecorator('age')
        age: number = 10;
        @MethodDecorator()
        greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
    }
}
// 抽象类和接口的区别
/**
 * 抽象类中不能被实例化,并且抽象类中可以有实现的方法和实例属性的初始化，但是接口不可以有实现的方法和实例属性的初始化
 */
namespace f {
    abstract class Animal {
        constructor(public name: string) { }
        abstract eat(): void;
        fly(): void { }
    }
    function double(val: number): number
    function double(val: string): string
    function double(val: any): any {
        if (typeof val == 'number') {
            return val * 2;
        }
        return val + val;
    }
    let r = double(1);
    console.log(r);
}
// 接口的三个特点
namespace g {
    // 1: 接口的第一个作用是用来描述对象
    interface Person {
        name: string;
        age: number;
        eat: () => void
        internet?: string
    }
    let p: Person = {
        name: '雷超',
        age: 27,
        // ha: 28,
        eat() {
            console.log('2222')
        }
    }
    // 2: 接口的第二个作用是用来实现继承，抽象一些公共的属性和方法

    interface Ha {
        // name:string;
        speak(): void;
        eat(): void;
    }
    class Lei implements Ha {
        speak(): void {
            throw new Error("Method not implemented.");
        }
        eat(): void {
            throw new Error("Method not implemented.");
        }
    }
    // 3: 接口的第三个作用是用来增强类
    // 无法预先知道有哪些新的属性的时候,可以使用 `[propName:string]:any`,propName名字是任意的
    interface Person2 {
        // readonly id: number;
        name: string;
        // [propName:string]:string
    }
    class Person2 {
        constructor(public age: number) { }
    }
    let p2: Person2 = new Person2(25)
    p2.name = '吴鹏丽'
    console.log(p2)
    // 函数类型接口
    interface discount {
        (price: number): number
    }
    let cost: discount = (price: number): number => {
        return price * 0.8
    }
    console.log(`折扣价${cost(6)}`)
}
namespace q {
    // 可索引接口,对数组和对象进行约束
    interface UserInterface {
        [index: number]: string
    }
    let arr: UserInterface = ['0', '1']
    let obj: UserInterface = { 0: 'leichao', 1: 'zs' } // 约束对象
    interface UserInterface2 {
        [index: string]: string
    }
    let obj2: UserInterface2 = {
        name: '哈哈'
    }
    interface Speakable {
        name: string;
        speak(words: string): void
    }
    class Dog implements Speakable {
        name: string;
        constructor(name: string) {
            this.name = name
        }
        speak(words: string): void {
            throw new Error("Method not implemented.");
        }
    }
}
console.log('=======泛型========')
/**
 * 泛型作用于函数,类, 接口。指的是预先不指定具体的类型,而是在使用的时候,再指定具体的类型
 * 泛型`T`作用域只限于函数内部使用
 */
namespace w {
    function createArray<T>(length: number, val: T): T[] {
        let result: T[] = []
        for (let index = 0; index < length; index++) {
            result[index] = val
        }
        return result
    }
    console.log(createArray(4, 'x'))
    // 类数组
    function sum(...ha: any) {
        let args: IArguments = arguments
        for (let i = 0; i < args.length; i++) {
            // console.log(args[i]);
        }
    }
    sum(1, 2, 3, 4, 5)
    // let root = document.getElementById('root');
    // // 断言为某种类型
    // let children: HTMLCollection = root!.children
    // let children2:HTMLCollection = (root as HTMLElement).children
    class MyPrivate<T>{
        public list: T[] = []
        add(val: T) { this.list.push(val) }
    }
    let my = new MyPrivate() // 我也可以不传泛型，让其自行判断
    my.add(12)
    my.add('sd')
    console.log(my.list)
    // 泛型接口，泛型用来约束函数
    interface Calculate {
        <T>(a: T, b: T): T
    }
    let addFunction: Calculate = function <T>(a: T, b: T): T {
        return a
    }
    addFunction<number>(1, 2)
    // 多个泛型参数,也就是泛型如何交换位置,交换变量做到了不用临时变量
    // 泛型也可以有默认值,也可以不传
    function wrap<A = number, B = string>(val: [A, B]): [B, A] {
        return [val[1], val[0]]
    }
    // 泛型也可以有默认值,也可以不传
    function wrap2<A, B>(val: [A, B]): [B, A] {
        return [val[1], val[0]]
    }
    wrap([1, '2'])
    wrap2([1, '2'])
    // 泛型约束,可以让泛型继承一个接口
    function logger<T>(a: T) {
        // console.log(a.length) // 由于定义的时候,并不知道这个泛型具体指向那个类型
    }
    interface LengthWise {
        length: number
        age: number
        name: string
    }
    function logger2<T extends LengthWise>(val: T) {
        console.log(val.age)
    }
    logger2({
        length: 20,
        age: 27,
        name: 'lei'
    })
    // type关键字是定义类型的
    type Gou = { list: [] } | []
    // 结合泛型定义更复杂的类型
    // 泛型类型别名
    type WuPeng<T> = { list: T[] } | T[]
    let w: WuPeng<string> = ['2']
    // type定义类型和interface定义接口的区别
    // type定义的类型不能继承和实现,type定义的类型更像是别名一样,创建接口可以在任意地方使用,
    // 接口创建了一个新的名字，而type类型并不创建新的名字,type更适合元组类型和联合类型等场景
}
namespace r {
    // 结构类型系统
    interface Animal {
        name: string
        age: number
    }
    interface Person {
        name: string
        age: number
        gender: number
    }
    // 函数传参的时候时候会判断接口的兼容性,何为Animal,也就是至少包含有name和age属性,可以多属性，但是不能少属性
    function getName(animal: Animal) {
        console.log(`姓名是:${animal.name}`)
    }
    // 接口类型的兼容性
    let p = {
        // let p: Animal= { // 在赋值的时候只会精确判断
        name: '吴鹏丽',
        age: 25,
        gender: '男性'
    }
    getName(p)
    let num2: { toString(): string } // 表示的是num2含有toString函数就行
    num2 = '1111'
    num2 = {} // 这个空对象也含有toString方法
    num2 = {
        toString() {
            return 'toString'
        }
    }
    num2 = '1111'
    // 在ts中只会对比结构系统，而不会在意类型，因为类型都是封装的
    class Animal {
        name: string
    }
    class Brid extends Animal {
        swing: string
    }
    let a: Animal;
    a = new Brid() // 并不是父类不能兼容子类，或者子类不能兼容父类的问题，而是ts只会对比结构
    let b: Brid;
    // b = new Animal()
    //甚至没有关系的两个类的实例也是可以的
    class Animal2 {
        name: string
    }
    class Bird2 {
        name: string
    }
    let c: Animal2;
    c = new Bird2();
    let d: Bird2;
    d = new Animal2();
    // 函数的兼容性： 函数的兼容性其实比较的是函数参数,函数参数可以少, 但返回值只能`相同或者更多`
    type sumFun = (a: number, b: number) => number
    let f1: sumFun;
    function sum1(a: number, b: number): number {
        return a + b
    }
    f1 = sum1 // 这sum1完全符合sumFun函数的定义
    function sum2(a: number, b: number): void {
        console.log('sss')
    }
    // f1 = sum2 返回值都不一样,肯定不行
    function sum3(a: number, b: number, c: number): number {
        return c
    }
    // f1 = sum3 多了参数，不行
    function sum4(a: number): number {
        return a
    }
    f1 = sum4 // 少了参数可以的
    function sum5(): number {
        return 1
    }
    f1 = sum5 // 没有参数也是可以的
    type LogFun = (a: number | string) => void
    let logFun: LogFun;
    function test1(a: number | string | boolean) {

    }
    logFun = test1 // 函数参数的协变
    // 泛型的兼容性
    interface Empty<T> {

    }
    let x!: Empty<string>;
    let y!: Empty<number>;
    x = y
    interface NotEmpty<T> {
        data: T
    }
    let x1!: NotEmpty<string>;
    let x2!: NotEmpty<string>;
    x1 = x2;
    x1 = { data: 's' }
    // 枚举内容的兼容性, 枚举内容与数字内容兼容,各个枚举内容不兼容
    enum Colors {
        yellow,
        red
    }
    let c1: Colors = 2
    let c2: number = Colors.yellow
    enum Genders {
        nan,
        nv
    }
    // let c1:Colors = Genders.nan 报错
    // 交叉类型
    interface Brid6 {
        name: string;
        fly(): void;
    }
    interface Person6 {
        name: string;
        talk(): void;
    }
    type BridPerson = Brid6 & Person6
    // 交叉类型的话，就是所有的属性都要包含
    let p1: BridPerson = {
        name: 'sss',
        talk() { },
        fly() { }
    }
    // 获取一个变量的类型
    type Person7 = { name: string, age: number, gender: string }
    let a1: Person7 = {
        name: '',
        age: 27,
        gender: '男'
    }
    let a2 = {
        a: '',
        b: 10,
        c: true
    }
    // ts增强了typeof
    type Ha = typeof a2
    // console.log(Person7 === type p1)
    // 通过索引[]可以获取一个类型的子类型

}
namespace t {
    // 通过索引[]可以获取一个类型的子类型
    interface Person {
        name: string;
        age: number;
        job: {
            name: string
        };
        interests: { name: string, level: number }[]
    }
    let FrontEndJob: Person['job'] = {
        name: '前端工程师'
    }
    let interestLevel: Person['interests'][0]['level'] = 2;
    // 有个需求: 根据传入的对象和对象获取其对应的值
    interface Lei {
        name: string
        age: number,
        interests: 'male' | 'female'
    }
    // key的类型必须是'name'|'age'|'interests'之一
    type LeiType = 'name' | 'age' | 'interests'
    type LeiType2 = keyof Lei
    function getValueByKey(obj: Lei, key: LeiType2): any {
        return obj[key]
    }
    let l1: Lei = {
        name: '吴鹏丽是傻逼',
        age: 30,
        interests: 'male'
    }
    console.log(getValueByKey(l1, 'name'))
    // 在定义的时候用in操作符去批量定义类型中的属性
    type PartLei = {
        [key in keyof Lei]?: Lei[key]
    }
    type Part<T> = {
        [key in keyof T]?: T[key]
    }
    let s1: Part<Lei> = { // 通过泛型也可以做到
        name: '',
        age: 24
    }
    let s2: PartLei = {
        name: '',
        age: 24,
        interests: 'male',
        // ha: ''
    }
    // ts内置Partial类型，可以帮我们做到把传入的属性由必填变为选填
    interface A {
        a1: string
        a2: number
    }

    type A1 = Partial<A>
    let a1: A1 = {
        a1: '',
        // d1: ''
    }
    // 我们来手动实现一下Partial 
    type Partial2<T> = {
        [key in keyof T]?: T[key]
    }
    // ts内置Required类型，可以帮我们做到把传入的属性由选填变为必填
    type Required2<T> = { // 自己实现的
        [key in keyof T]-?: T[key]
    }
    interface WuPengLi {
        name?: string, age: number, gender?: string
    }
    type Required2Temp = Required2<WuPengLi>
    let w1: Required2Temp = {
        name: '吳鵬麗',
        age: 25,
        gender: '女'
    }
    // Pick 能够帮助我们从传入的属性摘取
    interface Animal {
        name: string;
        age: number;
        gender: string
    }
    /**
     * From T pick a set of properties K
     * type Pick<T, K extends keyof T> = { [P in K]: T[P] };
     */
    // 摘取 Animal 中的 name 属性
    type AnimalSub = Pick<Animal, "name">; //{ name: string; }
    let a: AnimalSub = {
        name: 'aas'
        //   name:'zhufeng',
        //   age:10
    }
}
